/*
 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
 * Copyright (c) 2013 China Beijing Armink <armink.ztl@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * File: $Id: mbrtu_m.c,v 1.60 2013/08/17 11:42:56 Armink Add Master Functions $
 */

/* ----------------------- System includes ----------------------------------*/
#include "stdlib.h"
#include "string.h"

/* ----------------------- Platform includes --------------------------------*/
#include "port.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mb_m.h"
#include "mbrtu.h"
#include "mbframe.h"

#include "mbcrc.h"
#include "mbport.h"

#include "user_mb_app.h"

#if MB_MASTER_RTU_ENABLED > 0
/* ----------------------- Defines ------------------------------------------*/
#define MB_SER_PDU_SIZE_MIN 4   /*!< Minimum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_MAX 256 /*!< Maximum size of a Modbus RTU frame. */
#define MB_SER_PDU_SIZE_CRC 2   /*!< Size of CRC field in PDU. */
#define MB_SER_PDU_ADDR_OFF 0   /*!< Offset of slave address in Ser-PDU. */
#define MB_SER_PDU_PDU_OFF 1    /*!< Offset of Modbus-PDU in Ser-PDU. */

/* ----------------------- Type definitions ---------------------------------*/
typedef enum
{
    STATE_M_RX_INIT,  /*!< Receiver is in initial state. */
    STATE_M_RX_IDLE,  /*!< Receiver is in idle state. */
    STATE_M_RX_RCV,   /*!< Frame is beeing received. */
    STATE_M_RX_ERROR, /*!< If the frame is invalid. */
} eMBMasterRcvState;

typedef enum
{
    STATE_M_TX_IDLE, /*!< Transmitter is in idle state. */
    STATE_M_TX_XMIT, /*!< Transmitter is in transfer state. */
    STATE_M_TX_XFWR, /*!< Transmitter is in transfer finish and wait receive state. */
} eMBMasterSndState;

/* ----------------------- Static variables ---------------------------------*/
// static volatile eMBMasterSndState eSndState;
// static volatile eMBMasterRcvState eRcvState;
static volatile eMBMasterSndState eSndState[MB_MASTER_NUM];
static volatile eMBMasterRcvState eRcvState[MB_MASTER_NUM];

static volatile UCHAR ucMasterRTUSndBuf[MB_MASTER_NUM][MB_PDU_SIZE_MAX];
static volatile UCHAR ucMasterRTURcvBuf[MB_MASTER_NUM][MB_SER_PDU_SIZE_MAX];
static volatile USHORT usMasterSendPDULength[MB_MASTER_NUM];

static volatile UCHAR *pucMasterSndBufferCur[MB_MASTER_NUM];
static volatile USHORT usMasterSndBufferCount[MB_MASTER_NUM];

static volatile USHORT usMasterRcvBufferPos[MB_MASTER_NUM];
// static volatile BOOL   xFrameIsBroadcast = FALSE;
static volatile BOOL xFrameIsBroadcast[MB_MASTER_NUM] = {FALSE};

static volatile eMBMasterTimerMode eMasterCurTimerMode[MB_MASTER_NUM];

/* ----------------------- Start implementation -----------------------------*/
eMBErrorCode eMBMasterRTUInit(UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity, UCHAR ucMB_Number)
{
    eMBErrorCode eStatus = MB_ENOERR;
    ULONG usTimerT35_50us;

    ENTER_CRITICAL_SECTION();

    /* Modbus RTU uses 8 Databits. */
    if (xMBMasterPortSerialInit(ucPort, ulBaudRate, 8, eParity, ucMB_Number) != TRUE)
    {
        eStatus = MB_EPORTERR;
    }
    else
    {
        /* If baudrate > 19200 then we should use the fixed timer values
         * t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
         */
        if (ulBaudRate > 19200)
        {
            usTimerT35_50us = 35; /* 1800us. */
        }
        else
        {
            /* The timer reload value for a character is given by:
             *
             * ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
             *             = 11 * Ticks_per_1s / Baudrate
             *             = 220000 / Baudrate
             * The reload for t3.5 is 1.5 times this value and similary
             * for t3.5.
             */
            usTimerT35_50us = (7UL * 220000UL) / (2UL * ulBaudRate) + 20;
        }
        if (xMBMasterPortTimersInit((USHORT)usTimerT35_50us, ucMB_Number) != TRUE)
        {
            eStatus = MB_EPORTERR;
        }
    }
    EXIT_CRITICAL_SECTION();

    return eStatus;
}

void eMBMasterRTUStart(UCHAR ucMB_Number)
{
    ENTER_CRITICAL_SECTION();
    /* Initially the receiver is in the state STATE_M_RX_INIT. we start
     * the timer and if no character is received within t3.5 we change
     * to STATE_M_RX_IDLE. This makes sure that we delay startup of the
     * modbus protocol stack until the bus is free.
     */
    eRcvState[ucMB_Number] = STATE_M_RX_INIT;
    vMBMasterPortSerialEnable(TRUE, FALSE, ucMB_Number);
    vMBMasterPortTimersT35Enable(ucMB_Number);

    EXIT_CRITICAL_SECTION();
}

void eMBMasterRTUStop(UCHAR ucMB_Number)
{
    ENTER_CRITICAL_SECTION();
    vMBMasterPortSerialEnable(FALSE, FALSE, ucMB_Number);
    vMBMasterPortTimersDisable(ucMB_Number);
    EXIT_CRITICAL_SECTION();
}

eMBErrorCode eMBMasterRTUReceive(UCHAR *pucRcvAddress, UCHAR **pucFrame, USHORT *pusLength, UCHAR ucMB_Number)
{
    eMBErrorCode eStatus = MB_ENOERR;
    ENTER_CRITICAL_SECTION();
    assert_param(usMasterRcvBufferPos[ucMB_Number] < MB_SER_PDU_SIZE_MAX);
    /* Length and CRC check */
    if ((usMasterRcvBufferPos[ucMB_Number] >= MB_SER_PDU_SIZE_MIN) &&
        (usMBCRC16((UCHAR *)ucMasterRTURcvBuf[ucMB_Number], usMasterRcvBufferPos[ucMB_Number]) == 0))
    {
        /* Save the address field. All frames are passed to the upper layed
         * and the decision if a frame is used is done there.
         */
        *pucRcvAddress = ucMasterRTURcvBuf[ucMB_Number][MB_SER_PDU_ADDR_OFF];

        /* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
         * size of address field and CRC checksum.
         */
        *pusLength = (USHORT)(usMasterRcvBufferPos[ucMB_Number] - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC);

        /* Return the start of the Modbus PDU to the caller. */
        *pucFrame = (UCHAR *)&ucMasterRTURcvBuf[ucMB_Number][MB_SER_PDU_PDU_OFF];
    }
    else
    {
        eStatus = MB_EIO;
    }
    EXIT_CRITICAL_SECTION();
    if (eStatus)
    {
    }
    return eStatus;
}

eMBErrorCode eMBMasterRTUSend(UCHAR ucSlaveAddress, const UCHAR *pucFrame, USHORT usLength, UCHAR ucMB_Number)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT usCRC16;

    {
        if (ucSlaveAddress > MB_MASTER_TOTAL_SLAVE_NUM)
            return MB_EINVAL;
    }
    ENTER_CRITICAL_SECTION();

    /* Check if the receiver is still in idle state. If not we where to
     * slow with processing the received frame and the master sent another
     * frame on the network. We have to abort sending the frame.
     */
    if (eRcvState[ucMB_Number] == STATE_M_RX_IDLE)
    {
        /* First byte before the Modbus-PDU is the slave address. */
        pucMasterSndBufferCur[ucMB_Number]  = (UCHAR *)pucFrame - 1;
        usMasterSndBufferCount[ucMB_Number] = 1;

        /* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
        pucMasterSndBufferCur[ucMB_Number][MB_SER_PDU_ADDR_OFF] = ucSlaveAddress;
        usMasterSndBufferCount[ucMB_Number] += usLength;

        /* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
        usCRC16 = usMBCRC16((UCHAR *)pucMasterSndBufferCur[ucMB_Number], usMasterSndBufferCount[ucMB_Number]);

        ucMasterRTUSndBuf[ucMB_Number][usMasterSndBufferCount[ucMB_Number]++] = (UCHAR)(usCRC16 & 0xFF);
        ucMasterRTUSndBuf[ucMB_Number][usMasterSndBufferCount[ucMB_Number]++] = (UCHAR)(usCRC16 >> 8);

        /* Activate the transmitter. */
        eSndState[ucMB_Number] = STATE_M_TX_XMIT;
        vMBMasterPortSerialEnable(FALSE, TRUE, ucMB_Number);
    }
    else
    {
        eStatus = MB_EIO;
    }
    EXIT_CRITICAL_SECTION();
    return eStatus;
}

BOOL xMBMasterRTUReceiveFSM(UCHAR ucMB_Number)
{
    BOOL xTaskNeedSwitch = FALSE;
    UCHAR ucByte;

    assert_param((eSndState[ucMB_Number] == STATE_M_TX_IDLE) || (eSndState[ucMB_Number] == STATE_M_TX_XFWR));

    /* Always read the character. */
    (void)xMBMasterPortSerialGetByte((CHAR *)&ucByte, ucMB_Number);

    switch (eRcvState[ucMB_Number])
    {
            /* If we have received a character in the init state we have to
             * wait until the frame is finished.
             */
        case STATE_M_RX_INIT:
            vMBMasterPortTimersT35Enable(ucMB_Number);
            break;

            /* In the error state we wait until all characters in the
             * damaged frame are transmitted.
             */
        case STATE_M_RX_ERROR:
            vMBMasterPortTimersT35Enable(ucMB_Number);
            break;

            /* In the idle state we wait for a new character. If a character
             * is received the t1.5 and t3.5 timers are started and the
             * receiver is in the state STATE_RX_RECEIVCE and disable early
             * the timer of respond timeout .
             */
        case STATE_M_RX_IDLE:
            /* In time of respond timeout,the receiver receive a frame.
             * Disable timer of respond timeout and change the transmiter state to idle.
             */
            vMBMasterPortTimersDisable(ucMB_Number);
            eSndState[ucMB_Number] = STATE_M_TX_IDLE;

            usMasterRcvBufferPos[ucMB_Number]                                   = 0;
            ucMasterRTURcvBuf[ucMB_Number][usMasterRcvBufferPos[ucMB_Number]++] = ucByte;
            eRcvState[ucMB_Number]                                              = STATE_M_RX_RCV;

            /* Enable t3.5 timers. */
            vMBMasterPortTimersT35Enable(ucMB_Number);
            break;

            /* We are currently receiving a frame. Reset the timer after
             * every character received. If more than the maximum possible
             * number of bytes in a modbus frame is received the frame is
             * ignored.
             */
        case STATE_M_RX_RCV:
            if (usMasterRcvBufferPos[ucMB_Number] < MB_SER_PDU_SIZE_MAX)
            {
                ucMasterRTURcvBuf[ucMB_Number][usMasterRcvBufferPos[ucMB_Number]++] = ucByte;
            }
            else
            {
                eRcvState[ucMB_Number] = STATE_M_RX_ERROR;
            }
            vMBMasterPortTimersT35Enable(ucMB_Number);
            break;
    }
    return xTaskNeedSwitch;
}

BOOL xMBMasterRTUTransmitFSM(UCHAR ucMB_Number)
{
    BOOL xNeedPoll = FALSE;

    assert_param(eRcvState[ucMB_Number] == STATE_M_RX_IDLE);

    switch (eSndState[ucMB_Number])
    {
            /* We should not get a transmitter event if the transmitter is in
             * idle state.  */
        case STATE_M_TX_IDLE:
            /* enable receiver/disable transmitter. */
            vMBMasterPortSerialEnable(TRUE, FALSE, ucMB_Number);
            break;

        case STATE_M_TX_XMIT:
            /* check if we are finished. */
            if (usMasterSndBufferCount[ucMB_Number] != 0)
            {
                xMBMasterPortSerialPutByte((CHAR)*pucMasterSndBufferCur[ucMB_Number], ucMB_Number);
                pucMasterSndBufferCur[ucMB_Number]++; /* next byte in sendbuffer. */
                usMasterSndBufferCount[ucMB_Number]--;
            }
            else
            {
                xFrameIsBroadcast[ucMB_Number] =
                    (ucMasterRTUSndBuf[ucMB_Number][MB_SER_PDU_ADDR_OFF] == MB_ADDRESS_BROADCAST) ? TRUE : FALSE;
                /* Disable transmitter. This prevents another transmit buffer
                 * empty interrupt. */
                vMBMasterPortSerialEnable(TRUE, FALSE, ucMB_Number);
                eSndState[ucMB_Number] = STATE_M_TX_XFWR;
                /* If the frame is broadcast ,master will enable timer of convert delay,
                 * else master will enable timer of respond timeout. */
                if (xFrameIsBroadcast[ucMB_Number] == TRUE)
                {
                    vMBMasterPortTimersConvertDelayEnable(ucMB_Number);
                }
                else
                {
                    vMBMasterPortTimersRespondTimeoutEnable(ucMB_Number);
                }
            }
            break;
    }

    return xNeedPoll;
}

BOOL xMBMasterRTUTimerExpired(UCHAR ucMB_Number)
{
    BOOL xNeedPoll = FALSE;

    switch (eRcvState[ucMB_Number])
    {
            /* Timer t35 expired. Startup phase is finished. */
        case STATE_M_RX_INIT:
            xNeedPoll = xMBMasterPortEventPost(EV_MASTER_READY, ucMB_Number);
            break;

            /* A frame was received and t35 expired. Notify the listener that
             * a new frame was received. */
        case STATE_M_RX_RCV:
            xNeedPoll = xMBMasterPortEventPost(EV_MASTER_FRAME_RECEIVED, ucMB_Number);
            break;

            /* An error occured while receiving the frame. */
        case STATE_M_RX_ERROR:
            vMBMasterSetErrorType(EV_ERROR_RECEIVE_DATA, ucMB_Number);
            xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS, ucMB_Number);
            break;

            /* Function called in an illegal state. */
        default:
            assert_param((eRcvState[ucMB_Number] == STATE_M_RX_INIT) || (eRcvState[ucMB_Number] == STATE_M_RX_RCV) ||
                         (eRcvState[ucMB_Number] == STATE_M_RX_ERROR) || (eRcvState[ucMB_Number] == STATE_M_RX_IDLE));
            break;
    }
    eRcvState[ucMB_Number] = STATE_M_RX_IDLE;

    switch (eSndState[ucMB_Number])
    {
            /* A frame was send finish and convert delay or respond timeout expired.
             * If the frame is broadcast,The master will idle,and if the frame is not
             * broadcast.Notify the listener process error.*/
        case STATE_M_TX_XFWR:
            if (xFrameIsBroadcast[ucMB_Number] == FALSE)
            {
                vMBMasterSetErrorType(EV_ERROR_RESPOND_TIMEOUT, ucMB_Number);
                xNeedPoll = xMBMasterPortEventPost(EV_MASTER_ERROR_PROCESS, ucMB_Number);
            }
            break;
            /* Function called in an illegal state. */
        default:
            assert_param((eSndState[ucMB_Number] == STATE_M_TX_XFWR) || (eSndState[ucMB_Number] == STATE_M_TX_IDLE));
            break;
    }
    eSndState[ucMB_Number] = STATE_M_TX_IDLE;

    vMBMasterPortTimersDisable(ucMB_Number);
    /* If timer mode is convert delay, the master event then turns EV_MASTER_EXECUTE status. */
    if (eMasterCurTimerMode[ucMB_Number] == MB_TMODE_CONVERT_DELAY)
    {
        xNeedPoll = xMBMasterPortEventPost(EV_MASTER_EXECUTE, ucMB_Number);
    }

    return xNeedPoll;
}

/* Get Modbus Master send RTU's buffer address pointer.*/
void vMBMasterGetRTUSndBuf(UCHAR **pucFrame)
{
    *pucFrame = (UCHAR *)ucMasterRTUSndBuf;
}

/* Get Modbus Master send PDU's buffer address pointer.*/
void vMBMasterGetPDUSndBuf(UCHAR **pucFrame, UCHAR ucMB_Number)
{
    *pucFrame = (UCHAR *)&ucMasterRTUSndBuf[ucMB_Number][MB_SER_PDU_PDU_OFF];
}

/* Set Modbus Master send PDU's buffer length.*/
void vMBMasterSetPDUSndLength(USHORT SendPDULength, UCHAR ucMB_Number)
{
    usMasterSendPDULength[ucMB_Number] = SendPDULength;
}

/* Get Modbus Master send PDU's buffer length.*/
USHORT usMBMasterGetPDUSndLength(UCHAR ucMB_Number)
{
    return usMasterSendPDULength[ucMB_Number];
}

/* Set Modbus Master current timer mode.*/
void vMBMasterSetCurTimerMode(eMBMasterTimerMode eMBTimerMode, UCHAR ucMB_Number)
{
    eMasterCurTimerMode[ucMB_Number] = eMBTimerMode;
}

/* The master request is broadcast? */
BOOL xMBMasterRequestIsBroadcast(UCHAR ucMB_Number)
{
    return xFrameIsBroadcast[ucMB_Number];
}
#endif
